﻿using Lhz.Common.Business;
using Lhz.Data;
using Lhz.Data.Linq.Nh;
using Lhz.Framework.Test.Entity;
using Lhz.Test.Common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NHibernate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Transactions;

namespace Lhz.Framework.Test.LhzCommonBusiness
{
    [TestClass]
    public class UnitTestEntityProxy
    {
        private EntityProxy<Guid, Student> epStudent;
        private EntityProxy<Guid, Teacher> epTeacher;
        private List<Student> students;
        private List<Teacher> teachers;
        private List<Book> books;

        public UnitTestEntityProxy()
        {
            epStudent = new EntityProxy<Guid, Student>();
            epTeacher = new EntityProxy<Guid, Teacher>();
            ClearData();
        }

        private void AddData(int count = 5)
        {
            students = new List<Student>();
            teachers = new List<Teacher>();
            for (int i = 0; i < count; i++)
            {
                teachers.Add(EntityFactory.BuildTeacher());
            }
            epTeacher.Add(teachers);
            for (int i = 0; i < count; i++)
            {
                students.Add(EntityFactory.BuildStudent(teachers[i]));
            }
            books = new List<Book>();
            for (int i = 0; i < students.Count; i++)
            {
                var student = students[i];
                student.Teachers.Add(teachers[i]);
                for (int j = 0; j < 5; j++)
                {
                    student.Books.Add(EntityFactory.BuildBook(student));
                }
                for (int j = 0; j < 5; j++)
                {
                    var teacher = EntityFactory.BuildTeacher();
                    epTeacher.Add(teacher);
                    student.Teachers.Add(teacher);
                }
                student.HeadteacherGuid = teachers[i].Guid;
                student.Headteacher = teachers[i];
            }
            epStudent.Add(students);
        }

        private void ClearData()
        {
            epStudent.Delete(epStudent.Query());
            epTeacher.Delete(epTeacher.Query());
        }

        [TestMethod]
        public void EntityProxyTestMethodAddTestData()
        {
            AddData();
        }

        [TestMethod]
        public void EntityProxyTestMethodLazyProxyInitialize()
        {
            var headteacher = EntityFactory.BuildTeacher();
            epTeacher.Add(headteacher);
            var student = EntityFactory.BuildStudent(headteacher);
            epStudent.Add(student);
            using (var ep = new EntityProxy<Guid, Student>())
            {
                student = ep.QueryByPk(student.Guid);
                Assert.IsFalse(BuildCheckLambda<Student>("Headteacher").Invoke(student));
                Assert.IsFalse(BuildCheckLambda<Student>("Teachers").Invoke(student));
                Assert.IsFalse(BuildCheckLambda<Student>("Books").Invoke(student));

                BuildInitializeLambda<Student>("Headteacher").Invoke(student);
                BuildInitializeLambda<Student>("Teachers").Invoke(student);
                BuildInitializeLambda<Student>("Books").Invoke(student);

                Assert.IsTrue(BuildCheckLambda<Student>("Headteacher").Invoke(student));
                Assert.IsTrue(BuildCheckLambda<Student>("Teachers").Invoke(student));
                Assert.IsTrue(BuildCheckLambda<Student>("Books").Invoke(student));
            }

            epStudent.Delete(student);
        }

        private Func<T, bool> BuildCheckLambda<T>(string propertyName)
        {
            var entityType = typeof(T);
            var p = Expression.Parameter(entityType, "r");
            var pa = Expression.Property(p, entityType.GetProperty(propertyName));
            var mce = Expression.Call(typeof(NHibernateUtil).GetMethod("IsInitialized"), pa);
            return Expression.Lambda<Func<T, bool>>(mce, p).Compile();
        }

        private Action<T> BuildInitializeLambda<T>(string propertyName)
        {
            var entityType = typeof(T);
            var p = Expression.Parameter(entityType, "r");
            var pa = Expression.Property(p, entityType.GetProperty(propertyName));
            var mce = Expression.Call(typeof(NHibernateUtil).GetMethod("Initialize"), pa);
            return Expression.Lambda<Action<T>>(mce, p).Compile();
        }

        [TestMethod]
        public void EntityProxyTestMethodAddInTransaction()
        {
            using (var ts = new TransactionScope())
            {
                var epTeacher = new EntityProxy<Guid, Teacher>();
                var epStudent = new EntityProxy<Guid, Student>();
                var headTeacher = EntityFactory.BuildTeacher();
                epTeacher.Add(headTeacher);
                var student = EntityFactory.BuildStudent(headTeacher);
                epStudent.Add(student);
                ts.Complete();
            }
        }

        [TestMethod]
        public void EntityProxyTestMethodCrud()
        {
            var headteacher = EntityFactory.BuildTeacher();
            epTeacher.Add(headteacher);
            var student = EntityFactory.BuildStudent(headteacher);
            Dictionary<string, object> changes = new Dictionary<string, object>();
            changes.Add("Name", "Liu Hongzhe");
            var pct = new ProxyCrudTester<Guid, Student>();
            pct.CrudTest(student, student.Guid, changes, new string[] { "Headteacher", "Teachers", "Books" });
        }

        [TestMethod]
        public void EntityProxyTestMethodPagination()
        {
            AddData(35);
            var pagination = new Pagination() { Index = 3, Size = 10 };
            Assert.IsTrue(epStudent.Query(pagination).Count == 10);
            Assert.IsTrue(pagination.Count == students.Count);
            pagination = new Pagination() { Index = 4, Size = 10 };
            Assert.AreEqual(5, epStudent.Query(pagination).Count);
            Assert.AreEqual(students.Count, pagination.Count);
            ClearData();
        }

        [TestMethod]
        public void EntityProxyTestMethodOrderBy()
        {
            AddData();
            var orderBies = new List<OrderBy>();
            orderBies.Add(new OrderBy() { PropertyName = "Guid", OrderByMode = OrderByMode.Asc });
            orderBies.Add(new OrderBy() { PropertyName = "Name", OrderByMode = OrderByMode.Desc });
            var dbStudents = epStudent.Query(orderBies);
            for (int i = 0; i < dbStudents.Count - 2; i++)
            {
                var currentStudent = dbStudents[i];
                var nextStudent = dbStudents[i + 1];
                var compareGuid = string.Compare(currentStudent.Guid.ToString(), nextStudent.Guid.ToString());
                var compareName = string.Compare(currentStudent.Name.ToString(), nextStudent.Name.ToString());
                Assert.IsTrue(compareGuid < 0 || (compareGuid == 0 && compareName >= 0));
            }
            orderBies = new List<OrderBy>();
            orderBies.Add(new OrderBy() { PropertyName = "Guid", OrderByMode = OrderByMode.Desc });
            orderBies.Add(new OrderBy() { PropertyName = "Name", OrderByMode = OrderByMode.Asc });
            dbStudents = epStudent.Query(orderBies);
            for (int i = 0; i < dbStudents.Count - 2; i++)
            {
                var currentStudent = dbStudents[i];
                var nextStudent = dbStudents[i + 1];
                var compareGuid = string.Compare(currentStudent.Guid.ToString(), nextStudent.Guid.ToString());
                var compareName = string.Compare(currentStudent.Name.ToString(), nextStudent.Name.ToString());
                Assert.IsTrue(compareGuid > 0 || (compareGuid == 0 && compareName <= 0));
            }
            ClearData();
        }

        [TestMethod]
        public void EntityProxyTestMethodStudentWithBooks()
        {
            var teacher = EntityFactory.BuildTeacher();
            epTeacher.Add(teacher);
            var student = EntityFactory.BuildStudent(teacher);
            var books = new Dictionary<Guid, Book>();
            for (int i = 0; i < 5; i++)
            {
                var book = EntityFactory.BuildBook(student);
                books.Add(book.Guid, book);
            }
            var pctStudent = new ProxyCrudTester<Guid, Student>();
            var pctBook = new ProxyCrudTester<Guid, Book>();
            pctStudent.CrudWithOneToManyTest(CascadeMode.AllDeleteOrphan, student, student.Guid, "Books", books, "StudentGuid", pctBook);
            ClearData();
        }

        [TestMethod]
        public void EntityProxyTestMethodStudentWithTeacher()
        {
            var teachers = new Dictionary<Guid, Teacher>();
            for (int i = 0; i < 5; i++)
            {
                var teacher = EntityFactory.BuildTeacher();
                teachers.Add(teacher.Guid, teacher);
                epTeacher.Add(teacher);
            }
            var student = EntityFactory.BuildStudent(teachers.Values.ElementAt(0));
            var pctStudent = new ProxyCrudTester<Guid, Student>();
            var pctTeacher = new ProxyCrudTester<Guid, Teacher>();
            pctStudent.CrudWithManyToManyTest(CascadeMode.None, student, student.Guid, "Teachers", teachers, pctTeacher);
            ClearData();
        }

        [TestMethod]
        public void EntityProxyTestMethodQueryStudentWithLoadProxy()
        {
            AddData();
            List<Student> students = null;
            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList();
            }
            students.ClearProxy();
            foreach (var student in students)
            {
                Assert.IsNull(student.Books);
                Assert.IsNull(student.Teachers);
                Assert.IsNull(student.Headteacher);
            }

            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList();
                students.LoadProxy(new string[] { "Headteacher" });
            }
            students.ClearProxy();
            foreach (var student in students)
            {
                Assert.IsNull(student.Books);
                Assert.IsNull(student.Teachers);
                Assert.IsNotNull(student.Headteacher);
                Assert.IsNull(student.Headteacher.Students);
            }

            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList();
                students.LoadProxy(new string[] { "Headteacher.Students" });
            }
            students.ClearProxy();
            foreach (var student in students)
            {
                Assert.IsNull(student.Books);
                Assert.IsNull(student.Teachers);
                Assert.IsNotNull(student.Headteacher);
                Assert.IsNotNull(student.Headteacher.Students);
            }

            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList();
                students.LoadProxy(new string[] { "Teachers" });
            }
            students.ClearProxy();
            foreach (var student in students)
            {
                Assert.IsNull(student.Books);
                Assert.IsNotNull(student.Teachers);
                foreach (var teacher in student.Teachers)
                {
                    Assert.IsNull(teacher.Students);
                }
                Assert.IsNotNull(student.Headteacher);
            }

            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList();
                students.LoadProxy(new string[] { "Books" });
            }
            students.ClearProxy();
            foreach (var student in students)
            {
                Assert.IsNotNull(student.Books);
                Assert.IsNull(student.Teachers);
                Assert.IsNull(student.Headteacher);
            }

            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList();
                students.LoadProxy(new string[] { "Teachers.Students" });
            }
            students.ClearProxy();
            foreach (var student in students)
            {
                Assert.IsNull(student.Books);
                Assert.IsNotNull(student.Teachers);
                foreach (var teacher in student.Teachers)
                {
                    Assert.IsNotNull(teacher.Students);
                }
                Assert.IsNotNull(student.Headteacher);
            }

            using (var ep = new EntityProxy<Guid, Student>())
            {
                students = ep.CreateQueryable().ToList().LoadByPathsAndClearProxyAndRestoreProxyData(null);
            }
            foreach (var student in students)
            {
                Assert.IsNotNull(student.Books);
                Assert.IsNotNull(student.Teachers);
                Assert.IsNotNull(student.Headteacher);
                foreach (var teacher in student.Teachers.Union(new List<Teacher>() { student.Headteacher }))
                {
                    Assert.IsNotNull(teacher.Students);
                }
            }

            ClearData();
        }
    }
}
